import urllib.parse
from urllib.parse import urljoin
from typing import (
    Dict, 
    Optional, 
    List,
    Any,
    Union
)
from pydantic import (
    BaseModel, 
    Field,
    validator,
    HttpUrl
)
import asyncio
import aiohttp
import orjson
import aiofiles

from common.exception.errors import (
    ServerError
)
from common.enums import (
    CONTENT_TYPE,
    StrEnum
)
from common.log import log

class Method(StrEnum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"
    PATCH = "PATCH"
    HEAD = "HEAD"
    OPTIONS = "OPTIONS"

class Request(BaseModel):
    """统一请求参数模型，支持链式调用"""
    method: Method = Method.GET
    url: str = Field(description="请求路由")
    params: Optional[Dict[str, Any]] = Field(default_factory=dict, description="查询参数")
    headers: Optional[Dict[str, str]] = Field(default_factory=dict, description="请求头")
    data: Optional[Union[Dict[str, Any], str, bytes]] = Field(default=None, description="请求体数据")
    json_data: Optional[Any] = Field(default=None, description="允许任何可 JSON 序列化的对象")
    timeout: Optional[float] = Field(default=30.0, gt=0, description="超时时间")
    files: Optional[Dict[str, Any]] = None  # 支持文件上传

    class Config:
        arbitrary_types_allowed = True

    def with_params(self, **params) -> 'Request':
        """链式添加查询参数"""
        self.params.update(params)
        return self

    def with_headers(self, **headers) -> 'Request':
        """链式添加请求头"""
        self.headers.update(headers)
        return self

    def with_data(self, data: Union[Dict[str, Any], str, bytes]) -> 'Request':
        """链式设置请求体数据"""
        self.data = data
        return self

    def with_json(self, json_data: Any) -> 'Request':
        """链式设置 JSON 数据"""
        self.json_data = json_data
        return self

    def with_timeout(self, timeout: float) -> 'Request':
        """链式设置超时时间"""
        self.timeout = timeout
        return self

    def with_files(self, files: Dict[str, Any]) -> 'Request':
        """链式设置文件（用于表单上传）"""
        self.files = files
        return self

class Response(BaseModel):
    """统一响应模型"""
    status_code: int
    headers: Dict[str, str] = Field(default_factory=dict)
    data: Optional[Any] = None  # 解析后的 JSON 数据
    text: Optional[str] = None  # 原始文本响应
    content: Optional[bytes] = None  # 二进制内容

    def is_success(self) -> bool:
        """请求是否成功（2xx状态码）"""
        return 200 <= self.status_code < 300

    def raise_for_status(self) -> None:
        """如果请求失败，抛出异常"""
        if not self.is_success():
            raise ServerError(f"HTTP Error {self.status_code}: {self.text}")

class FormDataPart(BaseModel):
    """表单数据部分"""
    name: str
    value: Union[str, bytes, None] = None
    filename: Optional[str] = None
    content_type: Optional[str] = None

class HttpClient():
    """异步HTTP客户端，支持连接池、会话管理和多种数据格式
    
        使用示例
        
        async def example_usage():
            初始化客户端
            async with HttpClient(base_url="https://api.example.com") as client:
                JSON 请求
                json_request = Request(
                    method=Method.POST,
                    url="/users",
                    json_data={"name": "John", "email": "john@example.com"}
                )
                response = await client.execute(json_request)
                
                表单请求
                form_request = Request(
                    method=Method.POST,
                    url="/upload",
                    files={
                        "avatar": ("avatar.jpg", open("avatar.jpg", "rb").read(), "image/jpeg"),
                        "name": "John Doe"
                    }
                )
                response = await client.execute(form_request)
                
                文件下载
                await client.download_file(
                    url="/files/report.pdf",
                    save_path="./report.pdf",
                    progress_callback=lambda d, t: print(f"Downloaded: {d}/{t}")
                )
    """
    
    def __init__(
        self, 
        base_url: Optional[str] = None,
        default_timeout: float = 30.0,
        pool_size: int = 10,
        enable_compression: bool = True,
        verify_ssl: bool = True
    ):
        self.base_url = base_url
        self.default_timeout = default_timeout
        self._session: Optional[aiohttp.ClientSession] = None
        self.pool_size = pool_size
        self.enable_compression = enable_compression
        self.verify_ssl = verify_ssl
        
    async def __aenter__(self):
        await self.start()
        return self
        
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.close()
        
    async def start(self):
        """启动客户端，创建会话"""
        if self._session is None or self._session.closed:
            connector = aiohttp.TCPConnector(
                limit=self.pool_size,
                limit_per_host=10, # 新增：每主机最大连接数，建议值 5-20
                verify_ssl=self.verify_ssl
            )
            self._session = aiohttp.ClientSession(
                base_url=self.base_url,
                connector=connector,
                json_serialize=lambda x: orjson.dumps(x).decode(),
                timeout=aiohttp.ClientTimeout(total=self.default_timeout)
            )
            
    async def close(self):
        """关闭客户端，释放连接"""
        if self._session and not self._session.closed:
            await self._session.close()
            
    async def execute(self, request: Request) -> Response:
        """执行请求"""
        await self.start()
        
        # 准备请求参数
        kwargs = {
            'method': request.method.value,
            'url': self._simple_check_url(request.url),
            'params': request.params,
            'headers': request.headers,
            'timeout': aiohttp.ClientTimeout(total=request.timeout or self.default_timeout)
        }
                
        # 处理不同的数据格式
        if request.json_data is not None:
            kwargs['json'] = request.json_data
        elif request.data is not None:
            if isinstance(request.data, (dict, list)):
                kwargs['json'] = request.data
            else:
                kwargs['data'] = request.data
        elif request.files is not None:
            # 处理文件上传
            kwargs['data'] = await self._prepare_form_data(request.files)
        
        try:
            async with self._session.request(**kwargs) as response:
                content = await response.read()
                text = content.decode('utf-8', errors='ignore')
                
                # 尝试解析JSON
                data = None
                if response.headers.get('content-type', '').startswith('application/json'):
                    try:
                        data = orjson.loads(content)
                    except orjson.JSONDecodeError:
                        pass
                
                return Response(
                    status_code=response.status,
                    headers=dict(response.headers),
                    data=data,
                    text=text,
                    content=content
                )
                
        except aiohttp.ClientError as e:
            error_msg = f"Client error for {request.method.value} {request.url}: {e}"
            log.error(error_msg)
            raise ServerError(error_msg)
        
        except Exception as e:
            error_msg = f"Request error for {request.method.value} {request.url}: {e}"
            log.error(error_msg)
            raise ServerError(error_msg)
        
        
    def _simple_check_url(self, url: str) -> str:
        """简单校验 url"""
        if not url.startswith("/"):
            return f"/{url}"
        return url
            
    async def _prepare_form_data(self, files: Dict[str, Any]) -> aiohttp.FormData:
        """准备表单数据，支持文件上传"""
        form_data = aiohttp.FormData()
        
        for key, value in files.items():
            if isinstance(value, tuple):
                # 文件上传: (filename, content, content_type)
                if len(value) == 3:
                    filename, content, content_type = value
                    form_data.add_field(
                        key, 
                        content, 
                        filename=filename,
                        content_type=content_type
                    )
                elif len(value) == 2:
                    filename, content = value
                    form_data.add_field(key, content, filename=filename)
            elif hasattr(value, 'read'):
                # 文件类对象
                content = await self._read_file_like(value)
                form_data.add_field(key, content, filename=getattr(value, 'name', None))
            else:
                # 普通字段
                form_data.add_field(key, str(value))
                
        return form_data
    
    async def _read_file_like(self, file_obj) -> bytes:
        """读取文件类对象的内容"""
        if hasattr(file_obj, 'read'):
            if asyncio.iscoroutinefunction(file_obj.read):
                return await file_obj.read()
            else:
                return file_obj.read()
        raise ValueError("Unsupported file object")
    
    # 快捷方法
    async def get(self, url: str, **kwargs) -> Response:
        """发送GET请求"""
        request = Request(method=Method.GET, url=url, **kwargs)
        return await self.execute(request)
        
    async def post(self, url: str, **kwargs) -> Response:
        """发送POST请求"""
        request = Request(method=Method.POST, url=url, **kwargs)
        return await self.execute(request)
        
    async def put(self, url: str, **kwargs) -> Response:
        """发送PUT请求"""
        request = Request(method=Method.PUT, url=url, **kwargs)
        return await self.execute(request)
        
    async def delete(self, url: str, **kwargs) -> Response:
        """发送DELETE请求"""
        request = Request(method=Method.DELETE, url=url, **kwargs)
        return await self.execute(request)
    
    # 文件下载功能
    async def download_file(
        self,
        url: str,
        save_path: str,
        chunk_size: int = 8192,
        progress_callback: Optional[callable] = None,
        **kwargs
    ) -> bool:
        """
        下载文件到本地，支持进度显示
        """
        request = Request(method=Method.GET, url=url, **kwargs)
        response = await self.execute(request)
        
        if not response.is_success():
            raise ServerError(f"Download failed with status {response.status_code}")
        
        total_size = int(response.headers.get('content-length', 0))
        downloaded = 0
        
        try:
            async with aiofiles.open(save_path, 'wb') as f:
                # 如果内容已经在内存中（小文件）
                if response.content:
                    await f.write(response.content)
                    downloaded = len(response.content)
                    if progress_callback:
                        progress_callback(downloaded, total_size)
                else:
                    # 流式下载大文件
                    async for chunk in response.content.iter_chunked(chunk_size):
                        if not chunk:
                            break
                        await f.write(chunk)
                        downloaded += len(chunk)
                        if progress_callback:
                            progress_callback(downloaded, total_size)
            
            log.info(f"File downloaded successfully: {save_path}")
            return True
            
        except Exception as e:
            error_msg = f"File download error: {e}"
            log.error(error_msg)
            raise ServerError(error_msg)